<?php
/*
 * @Description    : 
 * @Version        : 1.0.0
 * @Author         : QianLong
 * @Date           : 3022-05-08 15:24:18
 * @LastEditors    : QianLong
 * @LastEditTime   : 2023-02-28 11:58:58
 */

namespace app\verify\controller;

ini_set('memory_limit', '256M');
ini_set('memory_limit', '-1');
use app\BaseController;
use app\common\RedisCache;
use app\lib\exception\ApiException;
use app\lib\Result;
use isszz\captcha\rotate\CaptchaException;
use isszz\captcha\rotate\facade\Captcha;
use think\facade\Validate;
use app\common\service\QlRedis;
use think\facade\Db;

use think\facade\Request;

class RotateCaptcha extends BaseController
{
    protected $redis;
    public function __construct()
    {
        $this->redis = new QlRedis;
    }
    private function checkSecure()
    {
        $dType = Request::header('D-Type');
        if ($dType != 'captcha.js' && $dType != 'captcha.vue') {
            throw new ApiException('error');
        }
    }
    private function timesLimit($key, $times, $time = '')
    {
        if (empty($time)) {
            $time = config('app.SYS_STATIC.redis_expire');
        }
        $cacheTTL = $this->redis->ttl('libs:verify:times:' . $key);
        if ($cacheTTL === -1 || $cacheTTL > $time) {
            $this->redis->del('libs:verify:times:' . $key);
        }
        if ($this->redis->exists('libs:verify:times:' . $key) > 0) {
            $count = $this->redis->get('libs:verify:times:' . $key);
            if ($count > $times) {
                throw new ApiException('验证请求过于频繁，请稍后再试');
            }
            $this->redis->incr('libs:verify:times:' . $key);
        } else {
            $this->redis->set('libs:verify:times:' . $key, 1, $time);
        }
    }
    public static function checkAppId($appId)
    {
        // 服务器版
        $hasSecret = (new RedisCache())->getSetCaptchaSecret($appId);
        if (!empty($hasSecret)) {
            $appIds[$appId]['appId'] = $appId;
            $appIds[$appId]['secret'] = $hasSecret;
            $appIds[$appId]['iv'] = substr($hasSecret, 8, 16);
            $appIds[$appId]['end_time'] = '3030-12-31';
        }
        if (!isset($appIds[$appId])) {
            throw new ApiException('appId不存在');
        }
        if (strtotime($appIds[$appId]['end_time']) < time()) {
            throw new ApiException('当前应用已过期');
        }
        return $appIds[$appId];
    }
    /**
     * 生成验证码图片和相关信息
     */
    public function rotate()
    {
        $this->checkSecure();
        self::checkAppId(input('param.appId'));
        // $this->insertLog('request',input('param.appId'));
        $image = UPLOAD_PATH . 'rotate_captcha_img/' . 's' . random_int(1, 22) . '.png';
        // setLang设置语言
        // $rotateCaptcha = Captcha::setLang('zh-cn');
        $data = Captcha::create(
            $image,
            UPLOAD_PATH . 'rotate_captcha_temp/' // 用于存储生成图片的目录
        )->get(260); // 260为最终生成的图片尺寸
        if (!$data) {
            return Result::Error('captcha error');
        }
        $result['img_id'] = $data['str'];
        $result['token'] = $data['token'];
        return Result::Success($result);
    }

    /**
     * 验证
     */
    public function verify()
    {
        $this->checkSecure();
        $info = self::checkAppId(input('param.appId'));
        $this->insertLog('verify',input('param.appId'));
        $rule = [
            'angle' => 'require',
            'token' => 'require',
        ];
        $data = [
            'angle'  => input('param.angle', '', 'trim'),
            'token'  => input('param.token', '', 'trim'),
        ];
        $validate = Validate::rule($rule);
        $result   = $validate->check($data);
        if (!$result) {
            $this->insertLog('verify_fail',input('param.appId'));
            return Result::Error($validate->getError());
        }
        try {
            if (Captcha::check($data['token'], $data['angle']) === true) {
                $this->insertLog('verify_success',input('param.appId'));
                $return['token'] = create_uuid('RT');
                $authenticate = openssl_encrypt(json_encode($data), 'aes-256-cbc', $return['token'], 0, $info['iv']);
                (new RedisCache())->getSetRotateCaptchaToken($return['token'], $authenticate);
                return Result::Success($return);
            }
        } catch (CaptchaException $e) {
            $ip = get_client_ip();
            $this->timesLimit($ip, 30, 60);
            $this->insertLog('verify_fail',input('param.appId'));
            return Result::Error($e->getMessage());
        }
        return Result::Error('error');
    }
    public function check()
    {
        $info = self::checkAppId(input('param.appId'));
        $this->insertLog('ticket',input('param.appId'));
        $rule = [
            'token' => 'require',
            'sign' => 'require',
        ];
        $data = [
            'token'  => input('param.token', '', 'trim'),
            'sign'  => input('param.sign', '', 'trim'),
        ];
        $validate = Validate::rule($rule);
        $result   = $validate->check($data);
        $ip = get_client_ip();
        if (!$result) {
            $this->timesLimit($ip, 30, 60);
            $this->insertLog('ticket_error',input('param.appId'));
            return Result::Error($validate->getError());
        }
        $sign = md5($data['token'] . $info['secret']);
        if ($sign != $data['sign']) {
            $this->timesLimit($ip, 30, 60);
            $this->insertLog('ticket_error',input('param.appId'));
            return Result::Error('签名不正确，验证失败');
        }
        $authenticate = (new RedisCache())->getSetRotateCaptchaToken($data['token']);
        if (empty($authenticate)) {
            $this->timesLimit($ip, 30, 60);
            $this->insertLog('ticket_error',input('param.appId'));
            return Result::Error('验证信息不正确，验证失败');
        }
        $checkData = openssl_decrypt($authenticate, 'aes-256-cbc', $data['token'], 0, $info['iv']);
        if (empty($checkData)) {
            $this->timesLimit($ip, 30, 60);
            $this->insertLog('ticket_error',input('param.appId'));
            return Result::Error('验证信息不正确，验证失败');
        }
        $checkData = json_decode($checkData, true);
        if (empty($checkData)) {
            $this->timesLimit($ip, 30, 60);
            $this->insertLog('ticket_error',input('param.appId'));
            return Result::Error('验证信息不正确，验证失败');
        }
        $this->insertLog('ticket_success',input('param.appId'));
        return Result::Success();
    }
    /**
     * 通过前端传递str字段给后端叫唤图片显示到前端
     */
    public function getImg()
    {
        self::checkAppId(input('param.appId'));
        $rule = [
            'img_id|图像ID' => 'require',
        ];
        $data = [
            'img_id'  => input('param.img_id', '', 'trim'),
        ];
        $validate = Validate::rule($rule);
        $result   = $validate->check($data);
        if (!$result) {
            return Result::Error($validate->getError());
        }
        [$mime, $image] = Captcha::output($data['img_id'], UPLOAD_PATH . 'rotate_captcha_temp/');
        if (empty($image)) {
            return 'null';
        }
        header('Content-type:' . $mime);
        header('Content-Length:' . strlen($image));
        header('Cache-Control: private, no-cache, no-store, must-revalidate');
        return $image;
    }
    public function insertLog($type,$appId)
    {
        // request=请求量，verify=验证量，verify_success=通过量，verify_fail=拦截量，ticket=票据校验量，ticket_success=票据校验通过，ticket_error=票据校验拦截
        $captchaInfo = (new RedisCache())->getSetCaptchaInfo($appId);
        $where['hour'] = date('YmdH');
        $where['app_id'] = $appId;
        $hasLog = Db::connect('mongodb')->name('captcha_log')->where($where)->find();
        if (empty($hasLog)) {
            $insert['_id'] = createId();
            $insert['date'] = date('Ymd');
            $insert['hour'] = date('YmdH');
            $insert['only_hour'] = date('H').':00';
            $insert['hour_stamp'] = strtotime(date('Y-m-d H').':00:00');
            $insert['month'] = date('Ym');
            $insert['year'] = date('Y');
            $insert['create'] = time();
            $insert['app_id'] = $appId;
            $insert['uid'] = (int) $captchaInfo['uid'];
            $insert['request'] = 0;
            $insert['verify'] = 0;
            $insert['verify_success'] = 0;
            $insert['verify_fail'] = 0;
            $insert['ticket'] = 0;
            $insert['ticket_success'] = 0;
            $insert['ticket_error'] = 0;
            $insert[$type] = 1;
            Db::connect('mongodb')->name('captcha_log')->insert($insert);
        }else{
            Db::connect('mongodb')->name('captcha_log')->where($where)->inc($type)->update();
        }
    }
}
